﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace FDSimpleModelGenerator.Common
{
    /// <summary>
    /// 表达式树
    /// </summary>
    public class ExpressionAgent
    {
        private ExpressionAgent() { }

        /// <summary>
        /// 当前对象
        /// </summary>
        public static ExpressionAgent Current => new ExpressionAgent();

        /// <summary>
        /// 创建对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="args"></param>
        /// <returns></returns>
        public T CreateInstance<T>(params object[] args)
        {
            var paramTypes = args.Select(a => a.GetType()).ToArray();
            var name = string.Join(',', paramTypes.Select(t => t.Name));
            // 从缓存中获取构造函数委托
            var key = $"{typeof(T).FullName}.{name}";
            var createInstanceDelegate = CacheManager.Default.Get(key, () => CreateInstanceDelegate(typeof(T), paramTypes),
                TimeSpan.FromMinutes(10));

            var result = createInstanceDelegate(args);
            return (T)Convert.ChangeType(result, typeof(T));
        }

        /// <summary>
        /// 根据类型创建构造函数委托
        /// </summary>
        /// <param name="type"></param>
        /// <param name="parameterTypes"></param>
        /// <returns></returns>
        public Func<object[], object> CreateInstanceDelegate(Type type, Type[] parameterTypes)
        {
            // 获取构造函数
            var constructor = type.GetConstructor(parameterTypes);
            // 创建lambda参数
            var lamParam = Expression.Parameter(typeof(object[]), "_args");
            // 构造函数参数表达式数组
            var constructorParam = BuildParameters(parameterTypes, lamParam);
            // 创建构造函数表达式
            NewExpression newExp = Expression.New(constructor, constructorParam);

            // 创建lambda表达式，返回构造函数
            Expression<Func<object[], object>> lambdaExp =
                Expression.Lambda<Func<object[], object>>(newExp, lamParam);

            return lambdaExp.Compile();
        }

        /// <summary>
        /// 根据类型数组和lambda表达式参数，转化参数成相应类型
        /// </summary>
        /// <param name="parameterTypes"></param>
        /// <param name="paramExp"></param>
        /// <returns></returns>
        private Expression[] BuildParameters(Type[] parameterTypes, ParameterExpression paramExp)
        {
            List<Expression> list = new List<Expression>();
            for (int i = 0; i < parameterTypes.Length; i++)
            {
                // 从参数表达式（参数是：object[]）中获取参数
                var arg = BinaryExpression.ArrayIndex(paramExp, Expression.Constant(i));
                // 把参数转化成指定类型
                var argCast = Expression.Convert(arg, parameterTypes[i]);
                list.Add(argCast);
            }

            return list.ToArray();
        }
    }
}
